/*
 * Galaxium Messenger
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Text;
using System.Collections.Generic;

using Anculus.Core;
using Galaxium.Core;

namespace Galaxium.Protocol.Irc
{
	public sealed class MessageBuffer
	{
		private byte[] _mem;

		private Queue<IrcMessage> _messages;

		private static readonly byte _cr = (byte)13;
		private static readonly byte _lf = (byte)10;
		private static readonly byte _space = (byte)32;
		private static readonly byte _colon  = (byte)58;

		public MessageBuffer(IConnection connection)
		{
			_messages = new Queue<IrcMessage> (8);
		}

		public bool IsMessageAvailable ()
		{
			return _messages.Count > 0;
		}

		public IrcMessage PeekMessage ()
		{
			if (_messages.Count > 0)
				return _messages.Peek ();
			return null;
		}

		public IrcMessage PopMessage ()
		{
			if (_messages.Count > 0)
				return _messages.Dequeue ();
			return null;
		}

		public void AppendData (byte[] data, int dlen)
		{
			AllocateMemory (data, dlen);

			while (true) {
				int crlfLength;
				int crlf = GetCrlfIndex (out crlfLength);
				if (crlf < 0) return;
				
				string ident = null;
				string command = null;
				int commandEndIndex;

				if (_mem[0] == _colon) {
					//the message starts with a colon, so it starts with the ident parameter
					
					int identEndIndex = GetSpaceIndex (0);
					commandEndIndex = GetSpaceIndex (identEndIndex + 1);
					
					ident = Encoding.UTF8.GetString (_mem, 1/*skip the colon*/, identEndIndex); 
					command = Encoding.UTF8.GetString (_mem, identEndIndex + 1, commandEndIndex - identEndIndex - 1).ToUpper (); 
				} else {
					//the message doesn't have ident info
					
					commandEndIndex = GetSpaceIndex (0);
					command = Encoding.UTF8.GetString (_mem, 0, commandEndIndex).ToUpper (); 
				}
				
				string[] parameters = GetMessageParameters (commandEndIndex + 1, crlf);
				
				Log.Info ("<< " + Encoding.UTF8.GetString (_mem, 0, crlf));
				
				FreeMemory (crlf + crlfLength);

				IrcMessage msg = IrcMessage.CreateMessageWithIdent (ident, command, parameters);
				
				_messages.Enqueue (msg);
			}
		}

		private string[] GetMessageParameters (int start, int crlf)
		{
			List<string> parameters = new List<string> ();
			
			int prev = start;
			for (int i=start; i<crlf; i++)
			{
				byte b = _mem[i];
				
				if (b == _space)
				{
					//normal parameter
					string param = Encoding.UTF8.GetString (_mem, prev, i-prev);
					parameters.Add (param);
					prev = i + 1;
				}
				else if (b == _colon)
				{
					//the last parameter if preceeded by a space
					if (_mem[i-1] == _space)
					{
						prev = i + 1;
						break; //the lastParam code handles this case
					}
				}
			}
			
			try
			{
				string lastParam = Encoding.UTF8.GetString (_mem, prev, crlf-prev);
				parameters.Add (lastParam);
			}
			catch (Exception ex)
			{
				Log.Debug ("Unable to obtain last parameter.");
			}
			
			return parameters.ToArray ();
		}

		private void FreeMemory (int skip)
		{
			int mlen = _mem == null ? 0 : _mem.Length;
			if (skip > mlen) skip = mlen;
			if (skip == mlen) { _mem = null; return; }

			byte[] buffer = new byte[mlen - skip];
			Array.Copy (_mem, skip, buffer, 0, mlen - skip);
			_mem = buffer;
		}

		private int GetCrlfIndex (out int crlfLength)
		{
			int mlen = _mem == null ? 0 : _mem.Length;
			for (int i = 0; i < mlen; i++) {
				byte b = _mem[i];

				if (b == _cr) { // \r or \r\n
					//check if there is a lf
					crlfLength = 1;
					if (i != (mlen - 1)) {
						byte la = _mem[i+1];
						if (la == _lf)
							crlfLength = 2;
					}
					return i;
				} else if (b == _lf) { // a single \n
					crlfLength = 1;
					return i;
				}
			}
			crlfLength = 0;
			return -1;
		}

		private int GetSpaceIndex (int start)
		{
			int mlen = _mem == null ? 0 : _mem.Length;
			for (int i = start; i < mlen; i++) {
				if (_mem[i] == _space)
					return i;
			}
			return -1;
		}

		private void AllocateMemory (byte[] data, int dlen)
		{
			int mlen = _mem == null ? 0 : _mem.Length;

			int newsize = dlen;
			if (_mem != null) newsize += mlen;

			byte[] buffer = new byte[newsize];
			if (_mem != null)
				Array.Copy (_mem, 0, buffer, 0, mlen);
			Array.Copy (data, 0, buffer, mlen, dlen);

			_mem = buffer;
		}
	}
}